#!/usr/bin/python
import pygtk
import gtk
import sys
import os
import re
import gettext
import time
import getopt

config_path = "/etc/launcher.conf"
# config_path = "launcher.conf"

icondirs = [ "/usr/share/icons/hicolor/48x48/apps/",
             "/usr/share/icons/Tango/48x48/apps/",
             "/usr/share/pixmaps/"]
             
# icondirs.__doc__ = """List of directories where to search icons files."""

gettext.install('screen-launcher')

def FindIcon(name):
  """Find an icon file for given name.
       Searches in the icondirs."""
  if os.path.exists(name):
    return name
  for dir in icondirs:
    if os.path.exists(dir+name):
      return dir+name

def button(label,onclick,data=None):
  """Create and return gtk.Button with given label. Clicking it will call onclick()."""
  b = gtk.Button(label)
  b.connect("clicked", onclick,data)
  b.show()
  return b

def stock_button(stock_id, onclick, data=None):
  """Create and return stock gtk.Button with given stock_id."""
  b = gtk.Button(stock=stock_id)
  b.connect("clicked", onclick, data)
  b.show()
  return b

def icon_button(stock_id, onclick, data=None):
  """Create and return a button with a stock icon only."""
  b = gtk.Button()
  i = gtk.Image()
  i.set_from_stock(stock_id, gtk.ICON_SIZE_SMALL_TOOLBAR)
  i.show()
  b.set_image(i)
  b.connect("clicked", onclick,data)
  b.show()
  return b

def image_button(image_path, label, onclick, data=None):
  """Create and return a button with icon (from image_path) and label."""
  b = gtk.Button()
  h = gtk.HBox(spacing=10)
  h.set_border_width(5)
  i = gtk.Image()
  i.set_from_file(image_path)
  i.show()
  h.pack_start(i,False)
  l = gtk.Label()
  l.set_use_markup(True)
  l.set_markup(label)
  l.show()
  h.pack_start(l,False)
  h.show()
  b.add(h)
  b.connect("clicked",onclick, data)
  b.show()
  return b

def image_toggle_button(image_path, label, onclick, data=None):
  """Create and return a toggle button with icon and label."""
  b = gtk.ToggleButton()
  l = gtk.Label()
  i = gtk.Image()
  h = gtk.HBox()
  i.set_from_file(image_path)
  i.show()
  l.set_use_markup(True)
  l.set_markup(label)
  l.show()
  h.pack_start(i)
  h.pack_start(l)
  h.show()
  b.add(h)
  b.connect("toggled", onclick,data)
  b.show()
  return b

def make_hbox(widget_list, spacing=5, gomogen=False, pack_end=False):
  """Create and return gtk.HBox, containing given widgets.
      Each element of widget_list may be a widget
      or a pair (widget, expand_flag)."""
  h = gtk.HBox(gomogen,spacing)
  for wd in widget_list:
    try:
      w = wd[0]
      f = wd[1]
    except:
      w = wd
      f = False
    if pack_end:
      h.pack_end(w,f)
    else:
      h.pack_start(w,f)
  h.show()
  return h

def make_vbox(widget_list, spacing=5, gomogen=False, pack_end=False):
  """Create and return gtk.VBox, containing given widgets.
      Each element of widget_list may be a widget
      or a pair (widget, expand_flag)."""
  h = gtk.VBox(gomogen,spacing)
  for wd in widget_list:
    try:
      w = wd[0]
      f = wd[1]
    except:
      w = wd
      f = False
    if pack_end:
      h.pack_end(w,f)
    else:
      h.pack_start(w,f)
  h.show()
  return h

class ProgramDialog:
  """Dialog for adding and editing of programs (launcher buttons)."""
  def __init__(self):
    self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
    self.window.set_title(_("Add program"))
    self.window.set_border_width(10)

    nbox = self.label_edit()

    ilbl = gtk.Label(_("Icon: "))
    ilbl.show()
    self.icon = gtk.Image()
    btnBrowse = button(_("Browse..."), self.browse, "icon")
    ibox = make_hbox([ilbl, (self.icon, True), btnBrowse])

    clbl = gtk.Label(_("Command: "))
    clbl.show()
    self.edCmd = gtk.Entry()
    self.edCmd.show()
    btnBrowseCmd = button(_("Browse..."), self.browse, "command")
    cbox = make_hbox([clbl, (self.edCmd, True), btnBrowseCmd])

    dlbl = gtk.Label(_("Description: "))
    dlbl.show()
    self.edDesc = gtk.Entry()
    self.edDesc.show()
    dbox = make_hbox([dlbl, (self.edDesc, True)])

    btnOk = stock_button(gtk.STOCK_OK, self.on_ok)
    btnCancel = stock_button(gtk.STOCK_CANCEL, self.cancel)
    okbox = make_hbox([btnOk, btnCancel], pack_end=True)

    vbox = make_vbox([nbox, ibox, cbox, dbox, okbox])
    self.window.add(vbox)

  def label_edit(self):
      namelbl = gtk.Label(_("Name: "))
      namelbl.show()
      self.edName = gtk.Entry()
      self.edName.show()
      nbox = make_hbox([namelbl, self.edName])
      return nbox

  def fill(self,prog):
    self.edName.set_text(prog['name'])
    self.icon_path = prog['icon']
    self.icon.set_from_file(self.icon_path)
    self.icon.show()
    self.edCmd.set_text(prog['command'])
    try:
      self.edDesc.set_text(prog['desc'])
    except:
      None

  def set_title(self,title):
    self.window.set_title(title)

  def browse(self,widget,data=None):
    chooser = gtk.FileChooserDialog("Select "+data, None, gtk.FILE_CHOOSER_ACTION_OPEN,(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK))
    filter = gtk.FileFilter()
    if data == "icon":
      filter.set_name(_("Images"))
      filter.add_mime_type("image/png")
      filter.add_mime_type("image/jpeg")
      filter.add_mime_type("image/xpm")
    else:
      filter.set_name(_("Executables"))
      filter.add_mime_type("application/x-executable")
      filter.add_mime_type("application/x-octet-stream")
    chooser.add_filter(filter)
    filter = gtk.FileFilter()
    filter.set_name(_("All files"))
    filter.add_pattern("*")
    chooser.add_filter(filter)
    response = chooser.run()
    if response == gtk.RESPONSE_OK:
      if data == "icon":
        self.icon_path = chooser.get_filename()
        self.icon.set_from_file(FindIcon(self.icon_path))
        self.icon.show()
      else:
        self.command = chooser.get_filename()
        self.edCmd.set_text(self.command)
    chooser.destroy()

  def on_ok(self,widget,data=None):
    if self.mode == "add":
      self.add()
    else:
      if self.mode == "edit":
        self.apply()

  def add(self):
    global config
    global wndMain
    prog = dict()
    prog['name'] = self.edName.get_text()
    if self.icon_path == None:
      dlg = gtk.MessageDialog(type=gtk.MESSAGE_ERROR, buttons=gtk.BUTTONS_CLOSE)
      dlg.set_markup(_("You must set icon for the program!"))
      r = dlg.run()
      dlg.destroy()
      return
    prog['icon'] = self.icon_path
    prog['command']  = self.edCmd.get_text()
    if self.edDesc.get_text() != "":
      prog['desc'] = self.edDesc.get_text()
    config[self.section].append(prog)
    self.window.hide()
    wndMain.refresh_programs_list(self.section)

  def apply(self):
    global config
    global wndMain
    prog = dict()
    prog['name'] = self.edName.get_text()
    prog['icon'] = self.icon_path
    prog['command']  = self.edCmd.get_text()
    prog['desc'] = self.edDesc.get_text()
    config[self.section][self.number] = prog
    self.window.hide()
    wndMain.refresh_programs_list(self.section)

  def cancel(self,widget,data=None):
    self.window.hide()

  # We recognize mode=="add" and mode=="edit"
  # number is number of program in this section, for mode=="edit".
  def show(self,section,mode,number=None):
    global config
    self.section = section
    self.mode = mode
    if mode == "edit":
      self.set_title(_("Edit program"))
      self.fill(config[section][number])
    self.number = number
    self.window.show()

class SectionDialog:
  """Dialog for adding and editing of sections."""
  def __init__(self):
    self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
    self.window.set_title(_("Add Section"))
    self.window.set_border_width(10)

    namelbl = gtk.Label(_("Name: "))
    namelbl.show()
    self.edName = gtk.Entry()
    self.edName.show()
    nbox = make_hbox([namelbl, (self.edName, True)])

    ilbl = gtk.Label(_("Icon: "))
    ilbl.show()
    self.icon_path = None
    self.icon = gtk.Image()
    btnBrowse = button(_("Browse..."), self.browse_icon)
    ibox = make_hbox([ilbl, (self.icon, True), btnBrowse])

    btnOk = stock_button(gtk.STOCK_OK, self.on_ok)
    btnCancel = stock_button(gtk.STOCK_CANCEL, self.cancel)
    okbox = make_hbox([btnOk, btnCancel], pack_end=True)

    vbox = make_vbox([nbox, ibox, okbox])
    self.window.add(vbox)

  def set_title(self,title):
    self.window.set_title(title)

  def browse_icon(self,widget,data=None):
    chooser = gtk.FileChooserDialog(_("Select icon"), None, gtk.FILE_CHOOSER_ACTION_OPEN,(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK))
    filter = gtk.FileFilter()
    filter.set_name(_("Images"))
    filter.add_mime_type("image/png")
    filter.add_mime_type("image/jpeg")
    filter.add_mime_type("image/xpm")
    chooser.add_filter(filter)
    filter = gtk.FileFilter()
    filter.set_name(_("All files"))
    filter.add_pattern("*")
    chooser.add_filter(filter)
    response = chooser.run()
    if response == gtk.RESPONSE_OK:
      self.icon_path = chooser.get_filename()
      self.icon.set_from_file(FindIcon(self.icon_path))
      self.icon.show()
    chooser.destroy()

  def on_ok(self,widget,data=None):
    if self.mode == "edit":
      self.apply()
    else:
      if self.mode == "add":
        self.add()

  def fill(self, section):
    global sicons
    self.edName.set_text(section)
    self.icon_path = sicons[section]
    self.icon.set_from_file(FindIcon(self.icon_path))
    self.icon.show()

  def add(self):
    global sicons
    global config
    global wndMain
    name = self.edName.get_text()
    if name in config:
      dlg = gtk.MessageDialog(type=gtk.MESSAGE_ERROR, buttons=gtk.BUTTONS_CLOSE)
      dlg.set_markup(_("You cannot add section with the same name: %s") % name)
      r = dlg.run()
      dlg.destroy()
      return
    if self.icon_path == None:
      dlg = gtk.MessageDialog(type=gtk.MESSAGE_ERROR, buttons=gtk.BUTTONS_CLOSE)
      dlg.set_markup(_("You must set section icon!"))
      r = dlg.run()
      dlg.destroy()
      return
    sicons[name] = self.icon_path
    config[name] = []
    self.window.hide()
    wndMain.refresh_sections_list()

  def apply(self):
    global sicons
    global wndMain
    name = self.edName.get_text()
    if name != self.old_section:
      del sicons[self.old_section]
      config[name] = config[self.old_section]
      del config[self.old_section]
    sicons[name] = self.icon_path
    self.window.hide()
    wndMain.refresh_sections_list()

  def cancel(self,widget,data=None):
    self.window.hide()

  
  def show(self, mode, section = None):
    """mode may be "edit" or "add"
    for mode=="edit", you must pass section."""
    self.mode = mode
    if mode == "edit":
      self.set_title(_("Edit section"))
      self.fill(section)
    self.section = section
    self.old_section = section
    self.window.show()

class ScreenLauncher:
  """Main class."""
  def __init__(self):
    self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
    self.window.connect("delete_event", self.on_delete)
    self.window.connect("destroy", self.on_close)
    self.window.set_title("Launcher")
    self.window.set_property("type-hint", gtk.gdk.WINDOW_TYPE_HINT_DESKTOP)
    self.window.fullscreen()
    screen_width = gtk.gdk.screen_width()
    screen_height = gtk.gdk.screen_height()
    self.window.set_decorated(False)
    self.window.resize(screen_width, screen_height)

    self.hbox = gtk.HBox(False,3)
    self.lpanel = gtk.ScrolledWindow()
    self.lpanel.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
    self.lpanel.set_border_width(5)
    self.rpanel = gtk.ScrolledWindow()
    self.rpanel.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
    self.rpanel.set_border_width(5)
    self.hbox.pack_start(self.lpanel,False)
    self.hbox.pack_end(self.rpanel)
    self.window.add(self.hbox)

    self.slist = gtk.VBox()
    self.plist = gtk.VBox()
    self.lpanel.add_with_viewport(self.slist)
    self.rpanel.add_with_viewport(self.plist)

    self.read_config()
    self.test_admin()
    self.fill_sections()
#    self.wp = gtk.Image()
#    try:
#      self.wp.set_from_file(self.wallpaper)
#    except:
#      pass
#    self.set_wallpaper()

    self.old_sect_button = None

    self.lpanel.show()
    self.rpanel.show()
    self.slist.show()
    self.plist.show()
    self.hbox.show()
    self.window.show()

#  def set_wallpaper(self):
#    pixbuf = gtk.gdk.pixbuf_new_from_file(self.wallpaper)
#    (pixmap,mask) = pixbuf.render_pixmap_and_mask()
#    self.window.style.bg_pixmap[gtk.STATE_NORMAL] = pixmap
#    self.window.queue_draw()

  def test_admin(self):
    try:
      fd = open(config_path,'aw')
      fd.close()
      self.admin = True
    except:
      self.admin = False

  def fill_sections(self):
    global config
    global sicons
    for section in config:
      btn = image_toggle_button(FindIcon(sicons[section]), "<b>%s</b>" % section, 
          self.section_clicked, section)
      btn.set_data("section", section)
      if self.admin:
        btnEdit = icon_button(gtk.STOCK_EDIT, self.edit_section, section)
        btnDel  = icon_button(gtk.STOCK_DELETE, self.remove_section, section)
        hbox = make_hbox([(btn,True), btnEdit,btnDel])
        self.slist.pack_start(hbox,False)
      else:
        self.slist.pack_start(btn, False)
    if self.admin:
      self.newsection = stock_button(gtk.STOCK_ADD, self.new_section)
      self.slist.pack_start(self.newsection,False)
    self.btnExit = stock_button(gtk.STOCK_QUIT, self.on_close)
    self.slist.pack_start(self.btnExit, False)

  def section_clicked(self,widget,section):
    if self.old_sect_button != None:
      self.old_sect_button.set_active(False)
    self.old_sect_button = widget
    self.section = section
    self.fill_programs_list(section)

  def on_program_button(self, button, prog):
    self.run_program(prog['command'])

  def move_program_up(self, button, num):
    global config
    if not (num-1 in range(len(config[self.section]))):
      return
    tmp = config[self.section][num]
    config[self.section][num] = config[self.section][num-1]
    config[self.section][num-1] = tmp
    self.fill_programs_list(self.section)

  def move_program_down(self, button, num):
    global config
    if not (num+1 in range(len(config[self.section]))):
      return
    tmp = config[self.section][num]
    config[self.section][num] = config[self.section][num+1]
    config[self.section][num+1] = tmp
    self.fill_programs_list(self.section)

  def fill_programs_list(self,data):
    global config
    self.plist.foreach(self.remove_pbutton)
    num = 0
    for prog in config[data]:
      if ('desc' in prog) and (prog['desc'] != ""):
        text = "<b>%s</b>\n%s" % (prog['name'], prog['desc'])
      else:
        text = "<b>%s</b>" % prog['name']
      btn = image_button(FindIcon(prog['icon']), text, self.on_program_button, prog)
      # Let's assosiate program data with launcher button
      btn.set_data("program",prog)
      if self.admin:
        btnUp = icon_button(gtk.STOCK_GO_UP, self.move_program_up, num)
        btnUp.set_tooltip_markup(_("Move program up"))
        btnDown = icon_button(gtk.STOCK_GO_DOWN, self.move_program_down, num)
        btnDown.set_tooltip_markup(_("Move program down"))
        btnEdit = icon_button(gtk.STOCK_EDIT, self.edit_program, num)
        btnEdit.set_tooltip_markup(_("Edit program\n%s") % prog['name'])
        btnDel  = icon_button(gtk.STOCK_DELETE, self.remove_program, num)
        btnDel.set_tooltip_markup(_("<b>Remove</b> program\n%s") % prog['name'])
        ubox = make_vbox([btnUp,btnDown],0)
        hbox = make_hbox([(btn,True), ubox, btnEdit,btnDel],0)
        self.plist.pack_start(hbox,False)
      else:
        self.plist.pack_start(btn,False)
      num = num + 1
    if self.admin:
      self.newprog = stock_button(gtk.STOCK_ADD, self.new_prog)
      self.plist.pack_start(self.newprog,False)

  def remove_pbutton(self, widget, data=None):
    self.plist.remove(widget)

  def remove_sbutton(self, widget, data=None):
    self.slist.remove(widget)

  def new_section(self, widget, data=None):
    asd = SectionDialog()
    asd.show("add")

  def new_prog(self, widget, data=None):
    apd = ProgramDialog()
    apd.show(self.section, "add")

  def edit_program(self, widget, n):
    apd = ProgramDialog()
    apd.show(self.section, "edit", n)

  def remove_program(self, widget, n):
    global config
    dlg = gtk.MessageDialog(type=gtk.MESSAGE_QUESTION, buttons=gtk.BUTTONS_YES_NO)
    dlg.set_markup(_("Do you really want to remove program '%s'?\nThis action is irrectractable.") % config[self.section][n]['name'])
    response = dlg.run()
    dlg.destroy()
    if response == gtk.RESPONSE_YES:
      del config[self.section][n]
      self.refresh_programs_list(self.section)

  def edit_section(self,widget,section):
    asd = SectionDialog()
    asd.show("edit", section)

  def remove_section(self,widget,section):
    global config
    global sicons
    dlg = gtk.MessageDialog(type=gtk.MESSAGE_QUESTION, buttons=gtk.BUTTONS_YES_NO)
    dlg.set_markup(_("Do you really want to remove section %s?\nAll programs from this section will be removed too!\nThis action is irrectractable.") % section)
    response = dlg.run()
    dlg.destroy()
    if response == gtk.RESPONSE_YES:
      del config[section]
      self.refresh_sections_list()

  def refresh_sections_list(self):
    self.slist.foreach(self.remove_sbutton)
    self.fill_sections()

  def refresh_programs_list(self,section):
    if section == self.section:
      self.plist.foreach(self.remove_pbutton)
      self.fill_programs_list(section)

  def run_program(self, command):
    os.system(command+" &")

  def read_config(self):
    global config
    global sicons
    global prog
    config = dict()
    prog = dict()
    sicons = dict()
    sre = re.compile("^\[(.*)\]$", re.L)
    namere = re.compile("^\s*Name\s*=\s*(.*)$", re.L)
    dscre  = re.compile("^\s*Description\s*=\s*(.*)$", re.L)
    cmdre  = re.compile("^\s*Command\s*=\s*(.*)$", re.L)
    iconre = re.compile("^\s*Icon\s*=\s*(.*)$", re.L)
    sicre  = re.compile("^\s*SectionIcon\s*=\s*(.*)$", re.L)
    wre    = re.compile("^\s*Wallpaper\s*=\s*(.*)$", re.L)
    cre = re.compile("\s*#.*$")
    fd = open(config_path, 'r')
    for line in fd:
      line = cre.sub("", line)
      if line == "":
        continue
      m=wre.match(line)
      if m:
        self.wallpaper = m.group(1)
      m=sre.match(line)
      if m:
        section = m.group(1)
        config[section] = []
      m=sicre.match(line)
      if m:
        sicons[section] = m.group(1)
      m=namere.match(line)
      if m:
        prog = dict()
        prog['name'] = m.group(1)
      m=dscre.match(line)
      if m:
        prog['desc'] = m.group(1)
      m=iconre.match(line)
      if m:
        prog['icon'] = m.group(1)
      m=cmdre.match(line)
      if m:
        prog['command'] = m.group(1)
        config[section].append(prog)
    fd.close()

  def save_config(self):
    global config
    global sicons
    cfg = open(config_path,"w")
    try:
      cfg.write("Wallpaper = %s\n\n" % self.wallpaper)
    except:
      None
    for section in config:
      cfg.write("[%s]\n" % section)
      cfg.write("SectionIcon = %s\n\n" % sicons[section])
      for prog in config[section]:
        cfg.write("Name = %s\n" % prog['name'])
        try:
          cfg.write("Description = %s\n" % prog['desc'])
        except:
          None
#           print "No description for %s" % prog['name']
        cfg.write("Icon = %s\n" % prog['icon'])
        cfg.write("Command = %s\n\n" % prog['command'])
    cfg.close()

  def main(self):
    gtk.main()

  def on_delete(self, widget,event,data=None):
    return not self.admin

  def on_close(self,widget, data=None):
    if self.admin:
      self.save_config()
    gtk.main_quit()

def usage():
  print "Synopsis: launcher.py [-c conffile]"
  print "  with -c option, you may point which config file to use."

if __name__ == "__main__":
  try:
    opts, args = getopt.getopt(sys.argv[1:], "hc:")
  except getopt.GetoptError:
    usage()
    sys.exit()
  for o,a in opts:
    if o == "-h":
      usage()
      sys.exit()
    if o == "-c":
      config_path = a
  wndMain = ScreenLauncher()
  wndMain.main()
